package czolgisci;

import java.awt.event.KeyEvent;
import java.util.ArrayList;

import utils.delegate.MethodDelegate;
import utils.input.KeyboardListener;
import utils.time.Clock;
import czolgisci.display.DisplayManager;
import czolgisci.display.GraphicInterface;
import czolgisci.display.ImagePanel;
import czolgisci.map.Map;
import czolgisci.map.MapManager;
import czolgisci.tiles.AMovableTile;
import czolgisci.tiles.factory.StandardTileFactory;

/**
 * Główna klasa gry - silnik. Zarządza grą, inicjuje wszystkie potrzebne
 * obiekty, wywołuje je w odpowiednim momencie.
 * 
 * obiekt tej klasa agreguje obiekty klas:
 * <ul>
 * <li>Engine (singleton) </li>
 * <li>LifeManager</li>
 * <li>KeyboardListener</li>
 * <li>MapManager</li>
 * <li>Clock</li>
 * </ul>
 * oraz asocjuje obiekt klasy GraphicInterface
 * 
 * @pattern
 * <ul>
 * <li>Singleton -ponieważ można stworzyć tylko jedną grę w czasie działania
 * programu</li>
 * <li>Fasada - udostępnia metody wysokiego poziomu do zarządzające grą, np.:
 * start, pause, resume</li>
 * </ul>
 * 
 * @author kasztan
 * @version 0.8
 */
public class Engine
{
	private static Engine instance;
	private GraphicInterface rootFrame;
	private Players playerLifesManager;
	private KeyboardListener keyListener;
	private MapManager tilesManager;
	private Clock interval;
	private DisplayManager displayManager;

	/**
	 * Prywatny konstruktor
	 * @see getInstance()
	 */
	private Engine()
	{
	}
	
	/**
	 * metoda wzorca Singleton
	 * @return jedyna instancja klasy
	 */
	static public Engine getInstance()
	{
		if (instance == null)
		{
			instance = new Engine();
		}
		return instance;
	}

	/**
	 * metoda wzorca Fasada, inicjalizuję klasę Engine i wszystkie inne
	 * niezbędne do funkcjonowania gry
	 * 
	 * @param root
	 *            instancja menedżera okienek
	 * @param obszar
	 *            na którym można rysować grę
	 * @param tablica
	 *            zawierająca nazwy graczy (np. Bolek, Lolek)
	 */
	public void init(GraphicInterface root, ImagePanel drawingArea,
			String playerNamesArr[])
	{
		rootFrame = root;
		// stworzenie zegara
		interval = new Clock();

		// stworzenie mapy
		tilesManager = new MapManager(new Map("res/maps/default.map", "res/img/backgrounds/default.png"),
				StandardTileFactory.getInstance());
		interval.addListener(tilesManager);
		
		// inicjalizacja tablicy graczy
		initializePlayers(playerNamesArr);
		
		// zarejestrowanie klawiszy
		keyListener = new KeyboardListener(root);
		registerKeysAndSetNames();
		interval.addListener(keyListener);

		// zarejestrowanie DisplayManager'a odpowiedzialnego za wyswietlanie gry
		displayManager = DisplayManager.getInstance();
		displayManager.init(tilesManager, drawingArea);
		interval.addListener(displayManager);
		
		start();
	}

	/**
	 * metoda wzorca Fasada, klasa Engine zarządza zakończeniem całej gry
	 */
	public void destroy()
	{
		interval.stop();
		rootFrame.endGame(playerLifesManager.getWinnerName());
		playerLifesManager.reset();
	}

	public void pauseOrResume()
	{
		if (interval.isPaused())
			resume();
		else
			pause();
	}
	
	/**
	 * zatrzymanie gry, pauza
	 */
	public void pause()
	{
		interval.pause();
	}

	/**
	 * przywrócenie gry po pauzie
	 */
	public void resume()
	{
		interval.resume();
	}

	/**
	 * rozpoczęcie gry
	 */
	public void start()
	{
		interval.start();
	}

	/**
	 * Tworzy graczy, dodaje pola wyświetlające liczbę żyć
	 * @param playerNamesArr
	 */
	private void initializePlayers(String[] playerNamesArr)
	{
		playerLifesManager = new Players();
		for (String name : playerNamesArr)
		{
			playerLifesManager.add(name);
			try
			{
				rootFrame.createLifeField(name, playerLifesManager
						.getLifes(name));
			} catch (IllegalArgumentException e)
			{
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Próbuje zakończyć grę. Kończy ją, gdy co najmniej jeden gracz ma zero żyć
	 */
	public void tryEndGame()
	{
		if (playerLifesManager.isEndOfGame())
		{
			destroy();
		}
	}
	
	/**
	 * usuwa życie graczowi
	 * @param name imię gracza
	 */
	public void removePlayerLife(String name)
	{
		try
		{
			int lifes = playerLifesManager.removeLife(name);
			rootFrame.updateLifeField(name, lifes);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * Rejestruje klawisze KeyListenera i przypisuje obiektom unikalne nazwy
	 */
	private void registerKeysAndSetNames()
	{
		try
		{
			ArrayList<AMovableTile> tanks = tilesManager.getTanks();
			int id = 0;
			AMovableTile tank = tanks.get(id);
			tank.setName(playerLifesManager.getPlayerName(id));
			MethodDelegate method = new MethodDelegate(tank, tank.getClass()
					.getMethod("moveNorth", new Class[0]), null);
			keyListener.registerKey('w', method);

			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"moveSouth", new Class[0]), null);
			keyListener.registerKey('s', method);

			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"moveWest", new Class[0]), null);
			keyListener.registerKey('a', method);

			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"moveEast", new Class[0]), null);
			keyListener.registerKey('d', method);
			
			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"shoot", new Class[0]), null);
			keyListener.registerKey(' ', method);

			id++;
			tank = tanks.get(id);
			tank.setName(playerLifesManager.getPlayerName(id));
			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"moveNorth", new Class[0]), null);
			keyListener.registerKey((char) KeyEvent.VK_UP, method);

			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"moveSouth", new Class[0]), null);
			keyListener.registerKey((char) KeyEvent.VK_DOWN, method);

			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"moveWest", new Class[0]), null);
			keyListener.registerKey((char) KeyEvent.VK_LEFT, method);

			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"moveEast", new Class[0]), null);
			keyListener.registerKey((char) KeyEvent.VK_RIGHT, method);
			
			method = new MethodDelegate(tank, tank.getClass().getMethod(
					"shoot", new Class[0]), null);
			keyListener.registerKey((char) KeyEvent.VK_CONTROL, method);
			
			method = new MethodDelegate(this, this.getClass().getMethod(
					"pauseOrResume", new Class[0]), null);
			keyListener.registerKey('p', method);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}
}
